// $codepro.audit.disable booleanMethodNamingConvention, unnecessaryCast
/**
 * Project: Utilities <br>
 * Package: org.leumasjaffe.functional.lazy
 */
package org.leumasjaffe.functional.lazy;

import static org.leumasjaffe.functional.CompareOperator.Compare.GREATER_THAN_OR_EQUAL;
import static org.leumasjaffe.functional.CompareOperator.Compare.LESS_THAN_OR_EQUAL;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.leumasjaffe.collections.Collect;
import org.leumasjaffe.functional.BinaryFunction;
import org.leumasjaffe.functional.BinaryOperator;
import org.leumasjaffe.functional.CompareOperator;
import org.leumasjaffe.functional.Consumer;
import org.leumasjaffe.functional.Predicate;
import org.leumasjaffe.functional.UnaryFunction;
import org.leumasjaffe.functional.UnaryOperator;

import com.google.common.base.Optional;

/**
 * Class org.leumasjaffe.functional.lazy..Lazy in file Lazy.java <br>
 * 
 * @author leumasjaffe
 * @version Nov 28, 2013
 * Created on: Nov 28, 2013
 * 
 * @param <T> The current type of elements in our collection should we evaluate
 * @param <B> The base type of our chain of lazy evaluations
 */
@NonNullByDefault
public class Lazy<T, B> implements LazyEval<T> {
	
	/**
	 * Obtains a lazy evaluator for our collection
	 * @param col A collection to pool from
	 * @param <T> The starting type of our chain of lazy evaluations
	 * @return A Lazy evaluator for your collection
	 */
	public static <T> LazyEval<T> from(Collection<T> col) {
		return new Lazy<T, T>(col);
	}
	
	/**
	 * @param advancer
	 * @param it
	 * @param monad
	 * @param accum
	 * @return The folded value
	 */
	@SuppressWarnings("null")
	private static final <B, R, S> S foldRight(StateAdvance<B, ?> advancer, Iterator<B> it,
			BinaryFunction<R, S, S> monad, S accum) {
		S result = accum;
		if (it.hasNext()) {
			final R temp = advancer.advanceAllStates(it.next());
			if (temp != null) {
				result = monad.apply(temp, foldRight(advancer, it, monad, accum));
			} else {
				result = foldRight(advancer, it, monad, accum);
			}
		}
		return result;
	}
	
	private final LazyRoot<B> root;
	@SuppressWarnings("null")
	private Collection<T> output = Collections.emptyList();
	
	protected @Nullable StateAdvance<T, ?> next = null;
	
	/**
	 * @param col
	 */
	@SuppressWarnings("unchecked")
	private Lazy(Collection<B> col) {
		this.root = new LazyRoot<B>((Lazy<B, B>) this, col);
	}
	
	/**
	 * @param root
	 */
	protected Lazy(LazyRoot<B> root) {
		this.root = root;
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#allMatch(org.leumasjaffe.functional.Predicate)
	 */
	@SuppressWarnings("all")
	@Override
	public boolean allMatch(Predicate<? super T> criteria) {
		return matchWith(Predicate.Not.of(criteria), false);
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#anyMatch(org.leumasjaffe.functional.Predicate)
	 */
	@Override
	public boolean anyMatch(Predicate<? super T> criteria) {
		return matchWith(criteria, true);
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#eval()
	 */
	@Override
	public Collection<T> eval() {
		cacheOutput();
		return this.output;
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#eval(java.lang.Class)
	 */
	@Override
	@SuppressWarnings("null")
	public <R extends Collection<T>> R eval(Class<R> type) {
		R returnCollection = null;
		try {
			returnCollection = type.newInstance();
			eval(returnCollection);
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return returnCollection;
	}

	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#eval(R)
	 */
	@Override
	public <R extends Collection<T>> R eval(R returnCollection) {
		final StateAdvance<B, ?> advancer = this.root.lazy.next;
		if (advancer != null) {
			final Iterator<B> it = this.root.collection.iterator();
			while (it.hasNext()) {
				final @Nullable B start = it.next();
				if (start != null) {
					final @Nullable T end = advancer.advanceAllStates(start);
					if (end != null) {
						returnCollection.add(end);
					}
				}
			}
		}
		return returnCollection;
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#filter(org.leumasjaffe.functional.Predicate)
	 */
	@Override
	public final LazyEval<T> filter(Predicate<? super T> criteria) {
		final LazyFilter<T, B> temp = new LazyFilter<T, B>(this.root, criteria);
		this.next = temp;
		return temp;
	}

	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#foldLeft(
	 * org.leumasjaffe.functional.BinaryOperator, T)
	 */
	@Override
	public <S> S foldLeft(BinaryFunction<S, T, S> folder, S accum) {
		return isNotCached() ? foldLeftMemoryless(folder, accum) :
			Collect.foldLeft(this.output, folder, accum);
	}
	

	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#foldRight(
	 * org.leumasjaffe.functional.BinaryOperator, T)
	 */
	@Override
	public <S> S foldRight(BinaryFunction<T, S, S> folder, S accum) {
		return isNotCached() ? foldRightMemoryless(folder, accum) :
			Collect.foldRightSafe(this.output, folder, accum);
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#forEach(org.leumasjaffe.functional.Consumer)
	 */
	@Override
	public final LazyEval<T> forEach(Consumer<? super T> mutator) {
		final LazyTransform<T, B> temp = new LazyTransform<T, B>(this.root, mutator);
		this.next = temp;
		return temp;
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#forEach(
	 * org.leumasjaffe.functional.UnaryOperator)
	 */
	@Override
	public final LazyEval<T> forEach(UnaryOperator<T> transformer) {
		return map(transformer);
	}

	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#map(org.leumasjaffe.functional.UnaryFunction)
	 */
	@Override
	public final <R> LazyEval<R> map(UnaryFunction<? super T, ? extends R> mapping) {
		final LazyMap<T, R, B> temp = new LazyMap<T, R, B>(this.root, mapping);
		this.next = temp;
		return temp;
	}

	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#max(java.util.Comparator)
	 */
	@Override
	public Optional<T> max(Comparator<? super T> comparator) {
		return most(new CompareOperator<T>(comparator, LESS_THAN_OR_EQUAL));
	}
	
	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#min(java.util.Comparator)
	 */
	@Override
	public Optional<T> min(Comparator<? super T> comparator) {
		return most(new CompareOperator<T>(comparator, GREATER_THAN_OR_EQUAL));
	}

	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#most(org.leumasjaffe.functional.BinaryFunction)
	 */
	@Override
	@SuppressWarnings("null")
	public Optional<T> most(final BinaryFunction<T, T, Boolean> cmp) {
		return isNotCached() ? mostMemoryless(cmp) : Collect.most(this.output.iterator(), cmp);
	}

	/* (non-Javadoc)
	 * @see org.leumasjaffe.functional.lazy.LazyEval#noneMatch(org.leumasjaffe.functional.Predicate)
	 */
	@Override
	public boolean noneMatch(Predicate<? super T> criteria) {
		return matchWith(criteria, false);
	}
	
	/**
	 * Implements the default evaluation function
	 * @return The end result collection
	 */
	@SuppressWarnings("null")
	private Collection<T> __eval() {
		Collection<T> returnCollection = null;
		final StateAdvance<B, ?> advancer = this.root.lazy.next;
		if (advancer != null) {
			returnCollection = eval((Class<? extends Collection<T>>) 
					this.root.collection.getClass());
		} else {
			returnCollection = (Collection<T>) this.root.collection;
		}
		return returnCollection;
	}

	/**
	 * Caches the result of evaluating this process
	 */
	private void cacheOutput() {
		if (this.output == Collections.emptyList()) { // $codepro.audit.disable useEquals
			synchronized (this) {
				if (this.output == Collections.emptyList()) { // $codepro.audit.disable useEquals
					this.output = __eval();
				}
			}
		}
	}

	/**
	 * Folds this lazy evaluation into  a final value if it has not already been evaluated.
	 * @param folder 
	 * @param accum
	 * @return
	 */
	private <S> S foldLeftMemoryless(BinaryFunction<S, T, S> folder, S accum) {
		S retVal = accum;
		final StateAdvance<B, ?> advancer = this.root.lazy.next;
		if (advancer != null) {
			@SuppressWarnings("null")
			final @NonNull Iterator<B> it = this.root.collection.iterator();
			retVal = foldLoop(advancer, it, folder, retVal);
		}
		return retVal;
	}

	/**
	 * @param advancer
	 * @param it
	 * @param monad
	 * @param accum
	 * @return
	 */
	private <S> S foldLoop(final StateAdvance<B, ?> advancer, final Iterator<B> it, 
			BinaryFunction<S, T, S> monad, S accum) {
		while (it.hasNext()) {
			final @Nullable B start = it.next();
			if (start != null) {
				final @Nullable T end = advancer.advanceAllStates(start);
				if (end != null) {
					accum = monad.apply(accum, end);
				}
			}
		}
		return accum;
	}

	/**
	 * @param advancer
	 * @param it
	 * @param monad
	 * @param accum
	 * @return The folded value
	 */
	private <S> S foldRight(StateAdvance<B, ?> advancer, ListIterator<B> it,
			BinaryFunction<T, S, S> monad, S accum) {
		S  retVal = accum;
		while (it.hasPrevious()) {
			final @Nullable B start = it.previous();
			if (start != null) {
				final @Nullable T end = advancer.advanceAllStates(start);
				if (end != null) {
					retVal = monad.apply(end, retVal);
				}
			}
		}
		return retVal;
	}

	/**
	 * @param monad
	 * @param accum
	 * @return
	 */
	private <S> S foldRightMemoryless(BinaryFunction<T, S, S> monad, S accum) {
		S  retVal = accum;
		final StateAdvance<B, ?> advancer = this.root.lazy.next;
		if (advancer != null) {
			if (this.root.collection instanceof List) {
				@SuppressWarnings("null")
				final @NonNull ListIterator<B> it = ((List<B>) this.root.collection).listIterator(
						this.root.collection.size());
				retVal = foldRight(advancer, it, monad, retVal);
			} else {
				@SuppressWarnings("null")
				final @NonNull Iterator<B> it = this.root.collection.iterator();
				retVal = foldRight(advancer, it, monad, retVal);
			}
		}
		return retVal;
	}

	/**
	 * @param advancer
	 * @param ret
	 * @param it
	 * @return
	 */
	private @Nullable T getFirst(final StateAdvance<B, ?> advancer,
			final Iterator<B> it) {
		T ret = null;
		while (it.hasNext() && ret == null) {
			final @Nullable B start = it.next();
			if (start != null) {
				ret = advancer.advanceAllStates(start);
			}
		}
		return ret;
	}
	
	/**
	 * @return
	 */
	private boolean isNotCached() {
		return this.output == Collections.emptyList(); // $codepro.audit.disable useEquals
	}

	/**
	 * @param criteria
	 * @param anyHits
	 * @return
	 */
	private boolean matchWith(Predicate<? super T> criteria, boolean anyHits) {
		final StateAdvance<B, ?> advancer = this.root.lazy.next;
		boolean toReturn = !anyHits;
		if (advancer != null) {
			final Iterator<B> it = this.root.collection.iterator();
			while (it.hasNext()) {
				final @Nullable B start = it.next();
				if (start != null) {
					final @Nullable T end = advancer.advanceAllStates(start);
					if (end != null && criteria.test(end)) {
						toReturn = anyHits;
						break;
					}
				}
			}
		}
		return toReturn;
	}
	
	/**
	 * @param cmp
	 * @return
	 */
	@SuppressWarnings("null")
	private Optional<T> mostMemoryless(final BinaryFunction<T, T, Boolean> cmp) {
		final StateAdvance<B, ?> advancer = this.root.lazy.next;
		T ret = null;
		if (advancer != null) {
			final Iterator<B> it = this.root.collection.iterator();
			if ((ret = getFirst(advancer, it)) != null) {
				ret = foldLoop(advancer, it, new BinaryOperator<T>() {
					@Override
					public T apply(T t1, T t2) {
						return cmp.apply(t1, t2) ? t1 : t2;
					}
				}, ret);
			}
		}
		return Optional.fromNullable(ret);
	}

}
